﻿/// <reference path="//Microsoft.WinJS.1.0/js/base.js" />

(function () {
    "use strict";

    // TODO: Log: document log.get error can mean log just doesn't exist. document logError

    WinJS.Namespace.define("Eight", (function createNS() {

        function setCss(element, propertyMap) {
            /// <summary>
            /// Sets CSS properties on a DOM element based on a object 'map'. Behaves
            /// similar to jQuery's .css() function.
            /// </summary>
            /// <param name="element">DOM Element</param>
            /// <param name="propertyMap">Object where keys map to properties on JavaScript DOM Elements' style object.</param>
            var p;

            if (!element || !element.style) {
                throw new Error("Eight.js: Invalid element encountered.");
            }

            for (p in propertyMap) {
                element.style[p] = propertyMap[p];
            }
        }

        function getControl(querySelector) {
            /// <summary>
            /// Gets a WinJS Control attached to a dom element by a query.
            /// </summary>
            /// <param name="querySelector">DOM Query to retrieve control element</param>
            /// <returns type="Object">The winControl property of retrieved element or null if not found or not initialized</returns>
            var elem = document.querySelector(querySelector);

            if (elem && elem.winControl) {
                return elem.winControl;
            }

            return null;
        }

        function showAppBar(querySelector) {
            /// <summary>
            /// If an appbar element can be found, shows it.
            /// </summary>
            /// <param name="querySelector" optional="true">DOM Query to retrieve control element</param>
            var control = querySelector ? getControl(querySelector) : getControl("div[data-win-control='WinJS.UI.AppBar']");

            if (control) {
                control.show();
            }
        }

        function Queue() {
            /// <summary>
            /// Creates a new instance of a Queue object.
            /// </summary>
            if (!(this instanceof Queue)) {
                return new Queue();
            }

            var _queue = [],
                _offset = 0;

            function enqueue(item) {
                _queue.push(item);
            }

            function dequeue() {
                var item;

                if (_queue.length === 0) {
                    return undefined;
                }

                item = _queue[_offset];
                _queue[_offset] = null;

                // If the empty part of the queue is greater than half the 
                //  size of the queue, then lob off the empty spots with slice.
                if (_queue.length <= (++_offset * 2)) {
                    _queue = _queue.slice(_offset);
                    _offset = 0;
                }

                return item;
            }

            function getLength() {
                return _queue.length - _offset;
            }

            return {
                enqueue: enqueue,
                dequeue: dequeue,
                getLength: getLength
            };
        }

        return {
            setCss: setCss,
            getControl: getControl,
            showAppBar: showAppBar,
            Queue: Queue
        };

    })());

    WinJS.Namespace.define("Eight.Binding", (function createNS() {

        var _propertyCache = {},
            TwoWay,
            BoolToDisplayBlockConverter,
            BoolToDisplayInlineConverter,
            BoolToDisplayGridConverter,
            BoolToDisplayFlexConverter,
            BoolToVisibilityConverter;

        function initProperty(name, defaultValue) {
            /// <summary>
            /// Creates a bindable property with a backing property.
            /// </summary>
            if (typeof (defaultValue) === "undefined") {
                defaultValue = null;
            }

            _propertyCache[name] = defaultValue;

            return new (function createProperty() {

                function get() {
                    return _propertyCache[name];
                }

                function set(value) {
                    var notifiable = this;

                    if (_propertyCache[name] === value) {
                        return;
                    }

                    _propertyCache[name] = value;

                    if (notifiable && notifiable.notify) {
                        notifiable.notify(name, value);
                    }
                }

                return {
                    get: get,
                    set: set,
                    enumerable: true
                };

            });
        }

        TwoWay = WinJS.Binding.initializer(function initializeTwoWayBinding(model, modelProperties, element, elementProperties) {
            /// <summary>
            /// Binds a value as two-way.
            /// </summary>
            // Create default one-way binding
            WinJS.Binding.defaultBind(model, modelProperties, element, elementProperties);
            // Add event listener to change event on element to update the model when value changes
            element.addEventListener("change", function propertyChanged() {
                var oldValue, newValue, i = 0;

                if (elementProperties.length === 1) {
                    newValue = element[elementProperties[0]];
                }
                else {
                    // Yes eval is evil, but we need this to evaluate the property correctly.
                    newValue = eval("element['" + elementProperties.join("']['") + "']");
                }
                oldValue = model[modelProperties[0]];
                if (oldValue !== newValue) {
                    model[modelProperties[0]] = newValue;
                }
            });
        });

        BoolToDisplayBlockConverter = WinJS.Binding.converter(function convertBoolToDisplay(val) {
            /// <summary>
            /// Converts a boolean to a CSS Display value.
            /// </summary>
            /// <returns>'Block' if value is truthy, 'None' if value is falsey.</returns>
            return val ? "block" : "none";
        });

        BoolToDisplayInlineConverter = WinJS.Binding.converter(function convertBoolToDisplay(val) {
            /// <summary>
            /// Converts a boolean to a CSS Display value.
            /// </summary>
            /// <returns>'Inline' if value is truthy, 'None' if value is falsey.</returns>
            return val ? "inline" : "none";
        });

        BoolToDisplayGridConverter = WinJS.Binding.converter(function convertBoolToDisplay(val) {
            /// <summary>
            /// Converts a boolean to a CSS Display value.
            /// </summary>
            /// <returns>'-ms-grid' if value is truthy, 'None' if value is falsey.</returns>
            return val ? "-ms-grid" : "none";
        });

        BoolToDisplayFlexConverter = WinJS.Binding.converter(function convertBoolToDisplay(val) {
            /// <summary>
            /// Converts a boolean to a CSS Display value.
            /// </summary>
            /// <returns>'-ms-flexbox' if value is truthy, 'None' if value is falsey.</returns>
            return val ? "-ms-flexbox" : "none";
        });

        BoolToVisibilityConverter = WinJS.Binding.converter(function convertBoolToDisplay(val) {
            /// <summary>
            /// Converts a boolean to a CSS Visibility value.
            /// </summary>
            /// <returns>'Visible' if value is truthy, 'Hidden' if value is falsey.</returns>
            return val ? "visible" : "hidden";
        });

        return {
            initProperty: initProperty,
            TwoWay: TwoWay,
            BoolToDisplayBlockConverter: BoolToDisplayBlockConverter,
            BoolToDisplayInlineConverter: BoolToDisplayInlineConverter,
            BoolToDisplayGridConverter: BoolToDisplayGridConverter,
            BoolToDisplayFlexConverter: BoolToDisplayFlexConverter,
            BoolToVisibilityConverter: BoolToVisibilityConverter
        };

    })());

    WinJS.Namespace.define("Eight.Log", (function createNS() {

        var LocalFolder = Windows.Storage.ApplicationData.current.localFolder,
            _queue = new Eight.Queue(),
            _logArea,
            _logTextArea,
            _isShowing = false,
            _lock = false,
            ActionType = {
                Log: 1,
                Clear: 2,
                Get: 3
            };

        function show() {
            /// <summary>
            /// Toggles the display of the log file.
            /// </summary>
            if (!_logArea) {
                _setupLogger();
            }

            if (!_isShowing) {
                get().done(function retrievedLog(logText) {
                    _logArea.style.display = "block";
                    WinJS.UI.Animation.fadeIn(_logArea);
                    _logTextArea.innerText = logText;
                },
                function (ex) {
                    _logArea.style.display = "block";
                    WinJS.UI.Animation.fadeIn(_logArea);
                    _logTextArea.innerText = "";
                });
            }
            else {
                _logArea.style.display = "none";
                WinJS.UI.Animation.fadeOut(_logArea);
            }

            _isShowing = !_isShowing;
        }

        function _copyLog() {
            /// <summary>
            /// Event handler for when the Copy icon is tapped. Copies text to the clipboard.
            /// </summary>
            window.clipboardData.setData("Text", _logText.join("\n"));
        }

        function _setupLogger() {
            /// <summary>
            /// Creates HTML elements in the DOM to display the log.
            /// </summary>
            var body = document.querySelector("body"),
                logCloseButton = document.createElement("div"),
                logCopyButton = document.createElement("div");

            _logArea = document.createElement("div");

            Eight.setCss(_logArea, {
                width: "640px",
                height: "480px",
                backgroundColor: "#fff",
                border: "1px solid #444",
                position: "absolute",
                top: "calc(50% - 240px)",
                right: "calc(50% - 320px)",
                opacity: "0",
                display: "none",
                overflow: "hidden"
            });

            _logArea.innerHTML = toStaticHTML([
                "<h1 class='win-type-x-large' style='color: #000; padding: 20px;'>Application Log</h1>",
                "<div style='width: calc(100% - 40px); height: calc(100%- 110px); background: #ddd; padding: 20px; overflow: auto; color: #000;' id='eightLogText'></div>"
            ].join(""));

            Eight.setCss(logCloseButton, {
                color: "#000",
                position: "absolute",
                top: "20px",
                right: "20px"
            });

            logCloseButton.className = "win-type-x-large";
            logCloseButton.innerHTML = toStaticHTML("&#xE106;");
            logCloseButton.onclick = show;
            _logArea.appendChild(logCloseButton);

            Eight.setCss(logCopyButton, {
                color: "#000",
                position: "absolute",
                top: "20px",
                right: "80px"
            });

            logCopyButton.className = "win-type-x-large";
            logCopyButton.innerHTML = toStaticHTML("&#xE16F;");
            logCopyButton.onclick = _copyLog;
            _logArea.appendChild(logCopyButton);

            body.appendChild(_logArea);
            _logTextArea = document.querySelector("#eightLogText");
        }

        function _dispatchNextAction() {
            /// <summary>
            /// Dispatches the next logging action in the queue. Only releases
            /// the lock after all actions in the queue have been completed.
            /// </summary>
            var next;

            if (_queue && _queue.getLength()) {
                next = _queue.dequeue();

                switch (next.type) {
                    case ActionType.Log:
                        return _internalLog(next.text, next);
                    case ActionType.Clear:
                        return clear();
                    case ActionType.Get:
                        return get();
                }
            }
            else {
                _lock = false;
            }
        }

        function _internalLog(text, deferred) {
            /// <summary>
            /// Handles the actual logging to the file. If passed a deferral
            /// will call the deferred's complete instead of returning
            /// a new promise.
            /// </summary>
            /// <param name="text">Text to be logged</param>
            /// <param name="deferred" optional="true">Deferral</param>
            /// <returns type="WinJS.Promise"></returns>
            return LocalFolder.createFileAsync("app.8log",
                Windows.Storage.CreationCollisionOption.openIfExists)
                .then(function (file) {
                    return Windows.Storage.FileIO.appendTextAsync(file, text);
                }).then(function () {
                    _dispatchNextAction();

                    if (deferred) {
                        deferred.complete();
                    }
                    else {
                        return WinJS.Promise.wrap();
                    }
                },
                function (ex) {
                    _dispatchNextAction();

                    return WinJS.Promise.wrapError(ex);
                });
        }

        function log(text) {
            /// <summary>
            /// Appends the text passed in as an argument to a log file in the local folder of the application
            /// called 'app.8log'.
            /// </summary>
            /// <param name="text"></param>
            /// <returns type="WinJS.Promise">appendTextAsync promise</returns>
            var deferred;

            try {

                if (!_lock) {
                    _lock = true;

                    return _internalLog(text);
                }
                else {
                    deferred = {
                        type: ActionType.Log,
                        text: text,
                        complete: null,
                        error: null
                    };

                    _queue.enqueue(deferred);

                    return new WinJS.Promise(function init(c, e) {
                        deferred.complete = c;
                        deferred.error = e;
                    });
                }
            }
            catch (ex) {
                return WinJS.Promise.wrapError(ex);
            }
        }

        function logError(error) {
            /// <summary>
            /// Appends the error passed in as an argument to a log file, formatted with the current date and time
            ///  in the local folder of the application called 'app.8log'.
            /// </summary>
            /// <param name="error" type="Error">Error object to be logged</param>
            /// <returns type="WinJS.Promise">appendTextAsync promise</returns>

            var message = Eight.String.format("{0} - {1}\n\r", new Date().toString(), error.stack);

            return Eight.Log.log(message);
        }

        function _internalClear(deferred) {
            /// <summary>
            /// Handles the actual delete of the file. If passed a deferral
            /// will call the deferred's complete instead of returning
            /// a new promise.
            /// </summary>
            /// <param name="deferred" optional="true">Deferral</param>
            /// <returns type="WinJS.Promise"></returns>
            return LocalFolder.getFileAsync("app.8log")
                .then(function (storageFile) {
                    return storageFile.deleteAsync(Windows.Storage.StorageDeleteOption.permanentDelete);
                },
                function fileDosNotExist(ex) {
                    console.log("Eight.js: The file 'app8.log' does not exist.");
                }).then(function () {
                    _dispatchNextAction();

                    if (deferred) {
                        deferred.complete();
                    }
                    else {
                        return WinJS.Promise.wrap();
                    }
                },
                function (ex) {
                    _dispatchNextAction();

                    return WinJS.Promise.wrapError(ex);
                });
        }

        function clear() {
            /// <summary>
            /// Deletes the current log file.
            /// </summary>
            /// <returns type="WinJS.Promise">File delete promise</returns>
            var deferred;

            try {
                if (!_lock) {
                    _lock = true;

                    return _internalClear();
                }
                else {
                    deferred = {
                        type: ActionType.Clear,
                        complete: null,
                        error: null
                    };

                    _queue.enqueue(deferred);

                    return new WinJS.Promise(function init(c, e) {
                        deferred.complete = c;
                        deferred.error = e;
                    });
                }
            }
            catch (ex) {
                return WinJS.Promise.wrapError(ex);
            }
        }

        function _internalGet(deferred) {
            /// <summary>
            /// Handles the actual get of the file. If passed a deferral
            /// will call the deferred's complete instead of returning
            /// a new promise.
            /// </summary>
            /// <param name="deferred" optional="true">Deferral</param>
            /// <returns type="WinJS.Promise"></returns>
            return LocalFolder.getFileAsync("app.8log")
                .then(function retrievedFile(file) {
                    return Windows.Storage.FileIO.readTextAsync(file);
                },
                function errorLoadingFile(ex) {
                    // This probably means nothing has been logged
                    return WinJS.Promise.wrapError(ex);
                })
                .then(function (text) {
                    _dispatchNextAction();

                    if (deferred) {
                        deferred.complete(text);
                    }
                    else {
                        return WinJS.Promise.wrap(text);
                    }
                },
                function (ex) {
                    _dispatchNextAction();

                    return WinJS.Promise.wrapError(ex);
                });
        }

        function get() {
            /// <summary>
            /// Retrieves the current log file contents.
            /// </summary>
            /// <returns type="WinJS.Promise">Log file promise</returns>
            var deferred;

            try {
                if (!_lock) {
                    _lock = true;

                    return _internalGet();
                }
                else {
                    deferred = {
                        type: ActionType.Get,
                        complete: null,
                        error: null
                    };

                    _queue.enqueue(deferred);

                    return new WinJS.Promise(function init(c, e) {
                        deferred.complete = c;
                        deferred.error = e;
                    });
                }
            }
            catch (ex) {
                return WinJS.Promise.wrapError(ex);
            }
        }

        return {
            log: log,
            logError: logError,
            clear: clear,
            get: get,
            show: show
        }

    })());

    // Note according to the UX Guidlines, its actually better to display
    //  messages inline if possible. Near the actual element/item causing
    //  the message to appear.
    WinJS.Namespace.define("Eight.Message", (function createNS() {

        var Notifications = Windows.UI.Notifications,
            NotificationManager = Notifications.ToastNotificationManager,
            ToastType = Notifications.ToastTemplateType,
            _queue = new Eight.Queue(),
            _msg,
            MessageType = {
                alert: 1,
                confirm: 2,
                custom: 3
            };

        function _appendCommands(md, options, onSelected) {
            /// <summary>
            /// Handles appending the list of buttons to the MessageDialog.
            /// </summary>
            /// <param name="md">MessageDialog</param>
            /// <param name="options">Button Labels</param>
            /// <param name="onSelected">Callback for when a button is selected</param>
            var cmd,
                i = 0,
                len = options.length;

            for (; i < len; i++) {
                cmd = new Windows.UI.Popups.UICommand();
                cmd.label = options[i];
                cmd.invoked = onSelected;
                md.commands.append(cmd);
            }
        }

        function _dispatchNextMessage() {
            /// <summary>
            /// Queue router for when multiple message functions have been invoked.
            /// </summary>
            var next;

            if (_queue && _queue.getLength()) {
                next = _queue.dequeue();
                switch (next.type) {
                    case MessageType.alert:
                        alertAsync(next.message, next.onClose);
                        break;
                    case MessageType.confirm:
                        confirmAsync(next.message, next.onClose);
                        break;
                    case MessageType.custom:
                        showMessageAsync(next.title, next.message, next.labels, next.onClose);
                        break;
                }
            }
        }

        function alertAsync(message, onClose) {
            /// <summary>
            /// Equivalent of window.alert. Alerts a message with an 'OK' button. This
            /// blocks the UI and thus provides a callback.
            /// </summary>
            /// <param name="message">String representing the text to display</param>
            /// <param name="onClose">Callback for when user dismisses the alert, label of button is the only parameter.</param>
            var result,
                resultOptions = ["OK"];

            // Treat _msg as a 'lock' to ensure we don't try to show
            //  two alerts at the same time.
            if (!_msg) {
                _msg = new Windows.UI.Popups.MessageDialog(message);

                _appendCommands(_msg, resultOptions, function (c) {
                    result = c.label;
                });

                _msg.showAsync().then(function (c) {
                    if (onClose) {
                        onClose(result);
                    }

                    _msg = null;
                    _dispatchNextMessage();
                });
            }
            else {
                _queue.enqueue({ message: message, onClose: onClose, type: MessageType.alert });
            }
        }

        function confirmAsync(message, onClose) {
            /// <summary>
            /// Equivalent of window.confirm. Prompts the user with an 'OK' and a 'Cancel' 
            /// button. This blocks the UI and thus provides a callback, with the result
            /// as the only callback parameter.
            /// </summary>
            /// <param name="message">String representing the text to display</param>
            /// <param name="onClose">Callback for when user interacts with the confirmation, true or false is returned as the only parameter.</param>
            var result,
                resultOptions = ["OK", "Cancel"];

            if (!_msg) {
                _msg = new Windows.UI.Popups.MessageDialog(message);

                _appendCommands(_msg, resultOptions, function (c) {
                    result = c.label === "OK" ? true : false;
                });

                _msg.showAsync().then(function (c) {
                    if (onClose) {
                        onClose(result);
                    }

                    _msg = null;
                    _dispatchNextMessage();
                });
            }
            else {
                _queue.enqueue({ message: message, onClose: onClose, type: MessageType.confirm });
            }
        }

        function showMessageAsync(title, message, buttonLabelsArray, onClose) {
            /// <summary>
            /// Presents the user with a Message Dialog with a custom title, message and buttons.
            /// </summary>
            /// <param name="title">String representing the title to display</param>
            /// <param name="message">String representing the text content to display</param>
            /// <param name="buttonLabelsArray">Array of button labels for the dialog</param>
            /// <param name="onClose">Callback for when user interacts with the dialog, clicked/tapped button's label is the only parameter.</param>
            var result;

            if (!_msg) {
                _msg = new Windows.UI.Popups.MessageDialog(message);

                _appendCommands(_msg, buttonLabelsArray, function (c) {
                    result = c.label;
                });

                _msg.showAsync().then(function (c) {
                    if (onClose) {
                        onClose(result);
                    }

                    _msg = null;
                    _dispatchNextMessage();
                });
            }
            else {
                _queue.enqueue({
                    title: title,
                    message: message,
                    labels: buttonLabelsArray,
                    onClose: onClose,
                    type: MessageType.custom
                });
            }
        }

        function ToastOptions(header, body, imgUri, launchData, scheduledTime, snoozeInterval, maxSnoozeCount) {
            /// <summary>
            /// Options class for toast notifications. Not all arguments are required, however, 
            /// scheduledTime is required for scheduled toasts.
            /// </summary>
            /// <param name="header" type="String" optional="true">Text displayed in toast</param>
            /// <param name="body" type="String" optional="true">Text displayed in toast</param>
            /// <param name="imgUri" type="String" optional="true">Uri to the image to be displayed in toast</param>
            /// <param name="scheduledTime" type="Date">Scheduled Date/Time to display the toast</param>
            /// <param name="snoozeInterval" type="Number" optional="true">The amount of time between occurrences of the notification.</param>
            /// <param name="maxSnoozeCount" type="Number" optional="true">The maximum number of times to display this notification.</param>
            /// <returns type=""></returns>
            if (!this instanceof ToastOptions) {
                return new ToastOptions(header, body, imgUri, launchData, scheduledTime, snoozeInterval, maxSnoozeCount);
            }

            this.header = header;
            this.body = body;
            this.imgUri = imgUri;
            this.launchData = launchData;
            this.scheduledTime = scheduledTime;
            this.snoozeInterval = snoozeInterval;
            this.maxSnoozeCount = maxSnoozeCount;
        }

        function _getScheduledToastFromOptions(toastXml, scheduledTime, snoozeInterval, maxSnoozeCount) {
            /// <summary>
            /// Picks the right scheduled toast notification depending on arguments.
            /// </summary>
            /// <param name="toastXml">Xml Document representing the toast to be displayed</param>
            /// <param name="scheduledTime" type="Date">Scheduled Date/Time to display the toast</param>
            /// <param name="snoozeInterval" type="Number" optional="true">The amount of time between occurrences of the notification.</param>
            /// <param name="maxSnoozeCount" type="Number" optional="true">The maximum number of times to display this notification.</param>
            /// <returns>ScheduledToastNotification</returns>
            if (!snoozeInterval && !maxSnoozeCount) {
                return new Notifications.ScheduledToastNotification(toastXml, scheduledTime);
            }
            else {
                return new Notifications.ScheduledToastNotification(toastXml, scheduledTime, snoozeInterval, maxSnoozeCount);
            }
        }

        function scheduleToastXml(toastXml, scheduledTime, snoozeInterval, maxSnoozeCount) {
            /// <summary>
            /// Presents a toast message using an XmlDocument. Useful for using a template
            /// other than those implemented in this library.
            /// </summary>
            /// <param name="toastXml">Formatted XmlDocument object to be toasted.</param>
            /// <param name="scheduledTime" type="Date">Date/Time to display the toast.</param>
            /// <param name="snoozeInterval" type="Number" optional="true">The amount of time between occurrences of the notification.</param>
            /// <param name="maxSnoozeCount" type="Number" optional="true">The maximum number of times to display this notification.</param>
            /// <returns type="WinJS.Promise">Returns a promise where done maps to onactivated and ondismissed.</returns>
            return new WinJS.Promise(function showToast(onComplete, onError) {
                var toast = _getScheduledToastFromOptions(toastXml, scheduledTime, snoozeInterval, maxSnoozeCount),
                    notifier = NotificationManager.createToastNotifier();

                try {
                    notifier.addToSchedule(toast);
                }
                catch (er) {
                    onError(er);
                }

                onComplete();
            });
        }

        function toastXml(toastXml) {
            /// <summary>
            /// Presents a toast message using an XmlDocument. Useful for using a template
            /// other than those implemented in this library.
            /// </summary>
            /// <param name="toastXml">Formatted XmlDocument object to be toasted.</param>
            /// <returns type="WinJS.Promise">Returns a promise where done maps to onactivated and ondismissed.</returns>
            return new WinJS.Promise(function showToast(onComplete, onError) {

                var toast = new Notifications.ToastNotification(toastXml),
                    notifier = NotificationManager.createToastNotifier();

                toast.onactivated = onComplete;
                toast.ondismissed = onComplete;
                toast.onfailed = onError;

                notifier.show(toast);

            });
        }

        function _getToastTemplateFromOptions(o) {
            /// <summary>
            /// Picks a toast xml template based on ToastOptions specified.
            /// </summary>
            /// <param name="o">ToastOptiosn object</param>
            /// <returns>Xml Document</returns>
            if ((o.body || o.header) && !(o.body && o.header) && !o.imgUri) {
                return NotificationManager.getTemplateContent(ToastType.toastText01);
            }
            else if ((o.body || o.header) && !(o.body && o.header) && o.imgUri) {
                return NotificationManager.getTemplateContent(ToastType.toastImageAndText01);
            }
            else if (o.body && o.header && !o.imgUri) {
                return NotificationManager.getTemplateContent(ToastType.toastText02);
            }
            else {
                return NotificationManager.getTemplateContent(ToastType.toastImageAndText02);
            }
        }

        function _getToastXmlFromOptions(o) {
            /// <summary>
            /// Parses options object into toast xml.
            /// </summary>
            /// <param name="o">ToastOptions object</param>
            /// <returns>Xml Document</returns>
            var template = _getToastTemplateFromOptions(o),
                textNodes = template.getElementsByTagName("text"),
                toastNode,
                launchAttribute,
                imgNode;

            // Add text
            if (o.header && o.body) {
                textNodes[0].appendChild(template.createTextNode(o.header));
                textNodes[1].appendChild(template.createTextNode(o.body));
            }
            else if (o.header || o.body) {
                textNodes[0].appendChild(template.createTextNode(o.body ? o.body : o.header));
            }
            else {
                throw new Error("Eight.js: Error in parsing toast options. Either a header, a body, or both must be specified");
            }

            // Add launch paylod
            if (o.launchData) {
                launchAttribute = template.createAttribute("launch");
                launchAttribute.value = o.launchData;
                toastNode = template.selectSingleNode("/toast");
                toastNode.attributes.setNamedItem(launchAttribute);
            }

            // Add image
            if (o.imgUri) {
                imgNode = template.getElementsByTagName("image")[0];
                imgNode.setAttribute("src", o.imgUri);
            }

            return template;
        }

        function toast(options) {
            /// <summary>
            /// Displays a Toast Notification. Template used is based off of what is specified on the options object.
            /// </summary>
            /// <param name="options">ToastOptions object or string of text to display.</param>
            /// <returns type="WinJS.Promise">Promise object where complete maps to onactivated/ondismissed.</returns>
            if (typeof (options) !== "object" && typeof (options) === "string") {
                options = new ToastOptions(options);
            }

            return toastXml(_getToastXmlFromOptions(options));
        }

        function scheduleToast(options) {
            /// <summary>
            /// Schedules a Toast Notification for a later date.
            /// </summary>
            /// <param name="options" type="ToastOptions">ToastOptions object. Must include scheduledTime parameter.</param>
            /// <returns type="WinJS.Promise">Promise object where complete fires after the toast is scheduled, not after the toast displays.</returns>
            return scheduleToastXml(
                _getToastXmlFromOptions(options),
                options.scheduledTime,
                options.snoozeInterval,
                options.maxSnoozeCount);
        }

        return {
            alertAsync: alertAsync,
            confirmAsync: confirmAsync,
            showMessageAsync: showMessageAsync,

            ToastOptions: ToastOptions,
            toast: toast,
            toastXml: toastXml,
            scheduleToast: scheduleToast,
            scheduleToastXml: scheduleToastXml
        };

    })());

    WinJS.Namespace.define("Eight.Network", (function createNS() {

        var NetworkInfo = Windows.Networking.Connectivity.NetworkInformation,
            NetworkLevel = Windows.Networking.Connectivity.NetworkConnectivityLevel,
            ConnectionProfile = Windows.Networking.Connectivity.ConnectionProfile,
            NetworkCostType = Windows.Networking.Connectivity.NetworkCostType;

        function isConnected() {
            /// <summary>
            /// Checks if device is connected to any network.
            /// </summary>
            /// <returns type="Boolean">True if connected to any network, false if not</returns>
            var level = getConnectionLevel();

            return level !== null && level !== NetworkLevel.none ? true : false;
        }

        function hasInternetAccess() {
            /// <summary>
            /// Checks if device is connected to the internet.
            /// </summary>
            /// <returns type="Boolean">True if connected to the internet, false if not</returns>
            var level = getConnectionLevel();

            return level === NetworkLevel.internetAccess;
        }

        function _isNetworkAdapterTypeMatch(desiredType) {
            /// <summary>
            /// Checks if the passed in type is a match for the current network adapter in use.
            /// </summary>
            /// <param name="desiredType">Integer representation of type to match</param>
            /// <returns type="Boolean"></returns>
            var profile = NetworkInfo.getInternetConnectionProfile(),
                interfaceType;

            if (profile) {
                interfaceType = profile.networkAdapter.ianaInterfaceType;

                return interfaceType === desiredType;
            }

            return false;
        }
        NetworkCostType.unrestricted
        function isOnWifi() {
            /// <summary>
            /// Checks if the device is connected via WiFi.
            /// </summary>
            /// <returns type="Boolean">True if connected via WiFi, false if not</returns>
            return _isNetworkAdapterTypeMatch(71); // wireless
        }

        function isOnEthernet() {
            /// <summary>
            /// Checks if the device is connected via Ethernet.
            /// </summary>
            /// <returns type="Boolean">True if connected via Ethernet, false if not</returns>
            return _isNetworkAdapterTypeMatch(6); // ethernet
        }

        function getConnectionLevel() {
            /// <summary>
            /// Gets the current level of the network connection.
            /// </summary>
            /// <returns type="Eight.Network.NetworkLevel">Connectivity Level</returns>
            var profile = NetworkInfo.getInternetConnectionProfile();

            return profile ? profile.getNetworkConnectivityLevel() : null;
        }

        function getConnectionCost() {
            /// <summary>
            /// Gets the current cost type of the network connection.
            /// </summary>
            /// <returns type="Eight.Network.NetworkCost">Network Cost</returns>
            var profile = NetworkInfo.getInternetConnectionProfile();

            return profile ? profile.getConnectionCost() : null;
        }

        return {
            Level: NetworkLevel,
            CostType: NetworkCostType,

            isConnected: isConnected,
            isOnWifi: isOnWifi,
            isOnEthernet: isOnEthernet,
            hasInternetAccess: hasInternetAccess,
            getConnectionLevel: getConnectionLevel,
            getConnectionCost: getConnectionCost
        };

    })());

    WinJS.Namespace.define("Eight.Settings", (function createNS() {

        var _areaCache;

        function Area(flyoutElementId, title, pageUrl) {
            /// <summary>
            /// Class to represent a settings area.
            /// </summary>
            /// <param name="flyoutElementId">Id of the flyout control in the settings html page</param>
            /// <param name="title">Title for the flyout</param>
            /// <param name="pageUrl">Path to the html page containing the flyout control</param>
            /// <returns type="Area"></returns>
            if (!(this instanceof Area)) {
                return new Area(flyoutElementId, title, pageUrl);
            }

            this.flyoutElementId = flyoutElementId;
            this.title = title;
            this.pageUrl = pageUrl;
        }

        function initialize(areas) {
            /// <summary>
            /// Attaches specified areas to the settings charm. Best if this is invoked on launch.
            /// </summary>
            /// <param name="areas">Array of Area classes</param>
            var i = 0,
                len;

            _areaCache = {};

            for (len = areas.length; i < len; i++) {
                _areaCache[areas[i].flyoutElementId] = { title: areas[i].title, href: areas[i].pageUrl };
            }

            WinJS.Application.onsettings = function onSettingsInvoked(e) {
                e.detail.applicationcommands = _areaCache;
                WinJS.UI.SettingsFlyout.populateSettings(e);
            };
        }

        function show(flyoutElementId) {
            /// <summary>
            /// Shows the settings flyout if called with no parameters, shows a specific
            /// settings area if called with the Id of the settings control.
            /// </summary>
            /// <param name="flyoutElementId" optional="true">Id of the settings area to show</param>
            var i = 0;

            if (!flyoutElementId) {
                WinJS.UI.SettingsFlyout.show();
            }
            else {
                if (!_areaCache[flyoutElementId]) {
                    throw new Error(Eight.String.format("Settings area with id '{0}' does not exist.", flyoutElementId));
                }

                WinJS.UI.SettingsFlyout.showSettings(flyoutElementId, _areaCache[flyoutElementId].href);
            }
        }

        return {
            Area: Area,
            initialize: initialize,
            show: show
        };

    })());

    WinJS.Namespace.define("Eight.String", (function createNS() {

        var trimRE = /^\s+|\s+$/g,
            formatRE = /\{(\d+)\}/g,
            slice = [].slice;

        function format(s, args) {
            /// <summary>
            /// Formats a string by replacing tokens such as {0} with their counterparts in the arguments.
            /// </summary>
            /// <param name="s">String to format</param>
            /// <param name="args">Array of values to insert</param>
            /// <returns type="String">Formatted string.</returns>
            args = slice.call(arguments, 1);

            return s.replace(formatRE, function (m, n) {
                if (m === "{{") {
                    return "{";
                }
                if (m === "}}") {
                    return "}";
                }
                return args[n];
            });
        }

        function isNullOrEmpty(s) {
            /// <summary>
            /// Checks if string is null or empty
            /// </summary>
            /// <param name="s">String to check</param>
            /// <returns type="Boolean">True if string is null or empty, false if not</returns>
            return (s === "" || s == null);
        }

        function padLeft(s, padChar, padCount) {
            /// <summary>
            /// Pads a string on the left with the given character by the given count
            /// </summary>
            /// <param name="s">String to pad</param>
            /// <param name="padChar">Character to pad with</param>
            /// <param name="padCount">Number of times to insert character</param>
            /// <returns type="String">Padded string</returns>
            var resultArray = [];

            while (padCount-- > 0) {
                resultArray.push(padChar);
            }

            resultArray.push(s);
            return resultArray.join("");
        }

        function padRight(s, padChar, padCount) {
            /// <summary>
            /// Pads a string on the right with the given character by the given count
            /// </summary>
            /// <param name="s">String to pad</param>
            /// <param name="padChar">Character to pad with</param>
            /// <param name="padCount">Number of times to insert character</param>
            /// <returns type="String">Padded string</returns>
            var resultArray = [s];

            while (padCount-- > 0) {
                resultArray.push(padChar);
            }

            return resultArray.join("");
        }

        return {
            format: format,
            isNullOrEmpty: isNullOrEmpty,
            padLeft: padLeft,
            padRight: padRight
        };

    })());

}());